/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.ComponentModel;
using System.Xml;

namespace DaveSexton.DocProject.DocSites.Configuration
{
	[DataObject]
	public sealed class DocSiteSettings : MarshalByRefObject
	{
		#region Public Properties
		public string DocSiteFramePath
		{
			get
			{
				return Read("docSiteFramePath");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("docSiteFramePath", value);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x")]
		public string CompiledHelp1xFilePath
		{
			get
			{
				return Read("compiledHelpFilePath");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("compiledHelpFilePath", value);
			}
		}

		public string HelpFileNotFoundPath
		{
			get
			{
				return Read("helpFileNotFoundPath");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("helpFileNotFoundPath", value);
			}
		}

		public string FileNotFoundPath
		{
			get
			{
				return Read("fileNotFoundPath");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("fileNotFoundPath", value);
			}
		}

		public string DocSiteIndexXmlSource
		{
			get
			{
				return Read("docSiteIndexXmlSource");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("docSiteIndexXmlSource", value);
			}
		}

		public string DocSiteContentsXmlSource
		{
			get
			{
				return Read("docSiteContentsXmlSource");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("docSiteContentsXmlSource", value);
			}
		}

		public bool SidebarHandlePersisted
		{
			get
			{
				return Read("sidebarHandlePersisted", true);
			}
			set
			{
				Write("sidebarHandlePersisted", value);
			}
		}

		public string LetterBar
		{
			get
			{
				return Read("letterBar");
			}
			set
			{
				Write("letterBar", value);
			}
		}

		public bool BrowseIndexEnabled
		{
			get
			{
				return Read("browseIndexEnabled", true);
			}
			set
			{
				Write("browseIndexEnabled", value);
			}
		}

		public int CreateIndexRefreshTimeout
		{
			get
			{
				return Read("createIndexRefreshTimeout", 1200);
			}
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueMustBePositiveInteger);

				Write("createIndexRefreshTimeout", value);
			}
		}

		public bool SearchEnabled
		{
			get
			{
				return Read("searchEnabled", true);
			}
			set
			{
				Write("searchEnabled", value);
			}
		}

		public string SearchPath
		{
			get
			{
				return Read("searchPath");
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				Write("searchPath", value);
			}
		}

		public int SearchMinimumKeywordLength
		{
			get
			{
				return Read("searchMinimumKeywordLength", 2);
			}
			set
			{
				if (value < 1)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueMustBePositiveInteger);

				Write("searchMinimumKeywordLength", value);
			}
		}

		public string SearchExcludedKeywords
		{
			get
			{
				return Read("searchExcludedKeywords");
			}
			set
			{
				Write("searchExcludedKeywords", value);
			}
		}

		public string SearchHotTitleKeywords
		{
			get
			{
				return Read("searchHotTitleKeywords");
			}
			set
			{
				Write("searchHotTitleKeywords", value);
			}
		}

		public float SearchEarlyKeywordWeightFactor
		{
			get
			{
				return Read("searchEarlyKeywordWeightFactor", 10F);
			}
			set
			{
				Write("searchEarlyKeywordWeightFactor", value);
			}
		}

		public float SearchTitleKeywordWeightFactor
		{
			get
			{
				return Read("searchTitleKeywordWeightFactor", 1.2F);
			}
			set
			{
				Write("searchTitleKeywordWeightFactor", value);
			}
		}

		public int SearchExactTitleHotKeywordMatchRank
		{
			get
			{
				return Read("searchExactTitleHotKeywordMatchRank", 250);
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueMustBePositiveIntegerOrZero);

				Write("searchExactTitleHotKeywordMatchRank", value);
			}
		}

		public int SearchPartialTitleHotKeywordMatchRank
		{
			get
			{
				return Read("searchPartialTitleHotKeywordMatchRank", 100);
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueMustBePositiveIntegerOrZero);

				Write("searchPartialTitleHotKeywordMatchRank", value);
			}
		}

		public int SearchExactTitleKeywordMatchRank
		{
			get
			{
				return Read("searchExactTitleKeywordMatchRank", 50);
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueMustBePositiveIntegerOrZero);

				Write("searchExactTitleKeywordMatchRank", value);
			}
		}

		public int SearchPartialTitleKeywordMatchRank
		{
			get
			{
				return Read("searchPartialTitleKeywordMatchRank", 20);
			}
			set
			{
				if (value < 0)
					throw new ArgumentOutOfRangeException("value", value, Resources.Errors.ValueMustBePositiveIntegerOrZero);

				Write("searchPartialTitleKeywordMatchRank", value);
			}
		}
		#endregion

		#region Private / Protected
		private readonly string path;
		private readonly XmlDocument config;
		private readonly IDictionary<string, object> values;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="DocSiteSettings" /> class with in-memory settings only.
		/// </summary>
		public DocSiteSettings()
		{
			values = new Dictionary<string, object>(0);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="DocSiteSettings" /> class for the configuration file at the
		/// specified <paramref name="path"/>.
		/// </summary>
		public DocSiteSettings(string path)
		{
			if (path == null)
				throw new ArgumentNullException("path");

			this.path = path;

			config = new XmlDocument();
			config.PreserveWhitespace = true;
			config.Load(path);
		}
		#endregion

		#region Methods
		private static TTo ConvertType<TFrom, TTo>(TFrom value)
		{
			if (typeof(TTo).IsAssignableFrom(typeof(TFrom)))
				return (TTo) (object) value;
			else if (value == null)
				return default(TTo);

			TypeConverter converter = TypeDescriptor.GetConverter(typeof(TTo));

			if (converter != null && converter.CanConvertFrom(typeof(TFrom)))
				return (TTo) converter.ConvertFrom(null, System.Globalization.CultureInfo.InvariantCulture, value);

			converter = TypeDescriptor.GetConverter(typeof(TFrom));

			if (converter != null && converter.CanConvertTo(typeof(TTo)))
				return (TTo) converter.ConvertTo(null, System.Globalization.CultureInfo.InvariantCulture, value, typeof(TTo));

			if (value is IConvertible)
				return (TTo) Convert.ChangeType(value, typeof(TTo), System.Globalization.CultureInfo.InvariantCulture);

			throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
				"The type, \"{0}\" cannot be automatically converted to \"{1}\".", typeof(TFrom), typeof(TTo)));
		}

		private string Read(string name)
		{
			return Read<string>(name, null);
		}

		private T Read<T>(string name, T defaultValue)
		{
			if (config == null)
			{
				if (values.ContainsKey(name))
					return (T) values[name];
				else
					return defaultValue;
			}
			else
			{
				XmlElement element = GetElement(name);

				if (element.IsEmpty || element.InnerText.Length == 0)
					return defaultValue;
				else
					return ConvertType<string, T>(element.InnerText);
			}
		}

		private void Write<T>(string name, T value)
		{
			if (config == null)
				values[name] = value;
			else
			{
				XmlElement element = GetElement(name);

				string converted = ConvertType<T, string>(value);

				element.InnerText = converted;
			}
		}

		private XmlElement GetElement(string name)
		{
			XmlElement element;
			XmlNodeList list = config.DocumentElement.GetElementsByTagName(name, "");

			if (list == null || list.Count == 0)
			{
				element = config.CreateElement("", name, "");
				config.DocumentElement.AppendChild(element);
			}
			else
				element = (XmlElement) list[0];

			return element;
		}

		public void Save()
		{
			if (config == null)
				throw new InvalidOperationException(Resources.Errors.CannotSaveInMemorySettings);

			config.Save(path);

			DocSiteManager.UpdateCachedSettings();
		}

		[DataObjectMethod(DataObjectMethodType.Update)]
		public void Update(DocSiteSettings settings)
		{
			if (settings.config != null)
				throw new ArgumentException(Resources.Errors.InMemorySettingsExpected, "settings");

			foreach (KeyValuePair<string, object> pair in settings.values)
				Write(pair.Key, pair.Value);
		}
		#endregion
	}
}
